/****************************************************************
** Product  :   HP Subscriber Network Application Policy
** Module   :   gr-manager
** Date: Apr 10, 2015               
** Author: Joey Yi
** (C) Copyright 2015, Hewlett-Packard Company, All Rights Reserved.
** This software is the proprietary information of HP, Inc.  
** Use is subject to license terms.
*****************************************************************
** Description:   
**  Restful interface utility class, Mainly process the VEM VoltDB Restful interface invocation.
****************************************************************/
package com.hp.snap.gr.utils;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.*;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.hp.snap.gr.exception.ActionException;
import com.hp.snap.gr.exception.RESTfulException;
import com.hp.snap.gr.model.*;

/**
 * Restful interface utility class, Mainly process the VEM VoltDB Restful interface invocation.
 *
 * <P>
 * Version: FIXME: 1.0.1 <BR>
 * Author: Joey Yi
 * Date: Apr 10, 2015
 * 
 * </P>
 *
 **/
public class RESTfulUtils {

    private static Logger logger = LoggerFactory.getLogger(RESTfulUtils.class);
    private static Pattern URL_PATTERN = Pattern.compile("http://([^/:]+):?(\\d+)?(.*)");
    /**
     *
     * Process the HttpResponse object and get the result.
     *
     * @param _host
     * @param _port
     * @param uri
     * @param actionMethod
     * @param body
     * @return
     * @throws RESTfulException
     */
    public static String getRESTfulInvokeResult(String _host, int _port,
                                                String uri, String actionMethod, String body) throws RESTfulException {
        String result;
        HttpResponse response = invokeRESTful(_host, _port, uri, actionMethod, body);
        HttpEntity entity = response.getEntity();
        int statusCode = response.getStatusLine().getStatusCode();
        try {
            result = EntityUtils.toString(entity);
        } catch (IOException e) {
            throw new RESTfulException(9999, e.getMessage(), e.getCause());
        }
        if (statusCode >= 400) {
            throw new RESTfulException(statusCode, result);
        } else {
            return result;
        }

    }

    public static int getRESTfulStatusCode(String _host, int _port,
                                           String uri, String actionMethod, String body) throws RESTfulException {

        HttpResponse response = invokeRESTful(_host, _port, uri, actionMethod, body);
        return response.getStatusLine().getStatusCode();
    }

    /**
     * 
     * Invoke Restful interface 
     *
     * @param _host  the hostname (IP or DNS name)
     * @param _port  the port number. -1 indicates the scheme default port.
     * @param uri     base URI
     * @param actionMethod    standard HTTP method
     * @param body     request parameters
     * @return  httpResponse
     * @throws RESTfulException
     */
    private static HttpResponse invokeRESTful(String _host, int _port,
                                              String uri, String actionMethod, String body) throws RESTfulException {

        HttpClient httpclient = getHttpClient();

        HttpResponse httpResponse = null;

        RESTfulMethod methodType = RESTfulMethod.valueOf(actionMethod);

        if (logger.isDebugEnabled()) {
            logger.debug("going to invoke REST service via {} http://{}:{}{}", new Object[]{methodType, _host, _port, uri});
            if (StringUtils.isNotEmpty(body)) {
                logger.trace("body content is {}", body);
            }
        }

        try {
            HttpHost target = new HttpHost(_host, _port, "http");
            switch (methodType) {
                case GET:
                    HttpGet getRequest = new HttpGet(uri + (StringUtils.isNotEmpty(body) ? "?" + body : ""));
                    httpResponse = httpclient.execute(target, getRequest);
                    break;
                case PUT:
                    // HttpPut putRequest = new HttpPut(uri + (StringUtils.isNotEmpty(body)?"?" + body:""));
                    HttpPut putRequest = new HttpPut(uri);
                    putRequest.setHeader("Content-Type", "application/json; charset=UTF-8");
                    setEntityForRequest(putRequest, body);
                    httpResponse = httpclient.execute(target, putRequest);
                    break;
                case POST:
                    HttpPost postRequest = new HttpPost(uri);
                    postRequest.setHeader("Content-Type", "application/json; charset=UTF-8");
                    setEntityForRequest(postRequest, body);
                    httpResponse = httpclient.execute(target, postRequest);
                    break;
                case DELETE:
                    HttpDelete deleteRequest = new HttpDelete(uri + (StringUtils.isNotEmpty(body) ? "?" + body : ""));
                    httpResponse = httpclient.execute(target, deleteRequest);
                    break;
                default:
                    logger.error("the method:" + actionMethod + " does not defined!");
            }
            if (logger.isTraceEnabled()) {
                logger.trace("HTTP response status: {} and content: {}", httpResponse.getStatusLine(), httpResponse.toString());
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new RESTfulException(e.getMessage(), e);
        }
        return httpResponse;
    }

    public static HttpClient getHttpClient() {
        String timeoutStr = GRMConfigManager.getLocalConfigValue(Constants.CFG_GRL_REST_REQUEST_TIMEOUT, "600");
        int timeout = Integer.parseInt(timeoutStr) * 1000;

        return HttpClientBuilder.create()
                .setDefaultRequestConfig(RequestConfig.custom()
                        .setConnectionRequestTimeout(timeout)
                        .setConnectTimeout(timeout)
                        .setSocketTimeout(timeout).build()).build();
    }

    private static void setEntityForRequest(HttpEntityEnclosingRequestBase request, String body) throws UnsupportedEncodingException,JSONException{
        if (StringUtils.isNotEmpty(body)) {
            String[] bo = body.split(",head\\:");
            if (bo.length == 2) {
                String header = bo[1];
                body = bo[0];
                JSONObject ob = new JSONObject(header);
                Iterator keys = ob.keys();
                while (keys.hasNext()) {
                    String head = (String) keys.next();
                    request.setHeader(head, ob.getString(head));
                }
            }
            StringEntity requestEntity = new StringEntity(
                    body,
                    "UTF-8");
            request.setEntity(requestEntity);
        } else {
            request.setEntity(null);
        }
    }

    ////////////////////////////////////// VoltDB Management //////////////////////////////////////////
    public static String invokeVEMRESTAPI(String uri, RESTfulMethod method) throws RESTfulException {
        return invokeVEMRESTAPI(GRMConfigManager.getSiteName(), uri, method);
    }

    public static String invokeVEMRESTAPI(String siteName, String uri, RESTfulMethod method) throws RESTfulException {
        String voltDBNodePath = Constants.CFG_GRG_ROOT + "/" + siteName + "/" + Constants.CFG_GRG_VOLTDB;
        logger.debug("[invokeVEMRESTAPI] input: siteName={}, uri={}, method={}", new Object[]{siteName, uri, method.toString()});
        Map<String, String> vemInfo = new HashMap<String, String>();
        try {
            vemInfo = getVemInfo();
        } catch (ActionException e) {
            throw new RESTfulException(e.getMessage(), e);
        }
        //        String vemHost = Utils.getVEMHostIP(siteName);
//        String vemPort = Utils.getConfigurationValue(voltDBNodePath, Constants.CFG_GRG_VEM_PORT);
        String vemRootURI = Utils.getConfigurationValue(Constants.CFG_GRG_VEM_URI);
        logger.debug("[invokeVEMRESTAPI] vemHost={}, vemPort={}, vemRootURI={}", new Object[]{vemInfo.get("vemHost"),vemInfo.get("vemPort") , vemRootURI});

        if (!vemRootURI.endsWith("/")) {
            vemRootURI = vemRootURI + "/";
        }

        if (uri.startsWith("/")) {
            uri = uri.replaceFirst("/", "");
        }

        String targetUri = vemRootURI + uri;

        return RESTfulUtils.getRESTfulInvokeResult(vemInfo.get("vemHost"), Integer.parseInt(vemInfo.get("vemPort")), targetUri, method.toString(), null);
    }

    public static int invokeVEMRESTAPIReturnResultCode(String siteName, String uri, RESTfulMethod method) throws RESTfulException {
        String voltDBNodePath = Constants.CFG_GRG_ROOT + "/" + siteName + "/" + Constants.CFG_GRG_VOLTDB;

        String vemHost = Utils.getVEMHostIP(siteName);
        String vemPort = Utils.getConfigurationValue(voltDBNodePath, Constants.CFG_GRG_VEM_PORT);
        String vemRootURI = Utils.getConfigurationValue(voltDBNodePath, Constants.CFG_GRG_VEM_URI);

        if (!vemRootURI.endsWith("/")) {
            vemRootURI = vemRootURI + "/";
        }

        if (uri.startsWith("/")) {
            uri = uri.replaceFirst("/", "");
        }

        String targetUri = vemRootURI + uri;
        logger.info("[invokeVEMRESTAPIReturnResultCode] targetUri=" + targetUri);
        return RESTfulUtils.getRESTfulStatusCode(vemHost, Integer.parseInt(vemPort), targetUri, method.toString(), null);
    }

    public static Database getVoltDBDatabase(String name) throws ActionException {
//        return getVoltDBDatabase(name, true);
        return getVoltDBDatabase(name, false);
    }

    public static Database getVoltDBDatabase(String name, boolean isCheckReplica) throws ActionException {
        String response = RESTfulUtils.invokeVEMRESTAPI("/mgmt/databases", RESTfulMethod.GET);
        Databases databases = new Gson().fromJson(response, Databases.class);
        for (Databasis databasis : databases.getDatabases()) {
            if (databasis.getDatabase().getName().equals(name)) {
                Database updateDatabase = databasis.getDatabase();
                String currentDBStatus = updateDatabase.getStatus();
                if (isCheckReplica && (Constants.VDB_STATUS_ONLINE.equals(currentDBStatus)
                        || Constants.VDB_STATUS_PAUSED.equals(currentDBStatus))) {
                    updateDatabase.setReplica(VoltDBStoreProcedureController.isReplica(updateDatabase, GRMConfigManager.getSiteName()));
                }
                return updateDatabase;
            }
        }
        return null;
    }

    public static Map<String, Database> getVoltDBDatabases(String remoteSiteName) throws ActionException {
        String response = RESTfulUtils.invokeVEMRESTAPI(remoteSiteName, "/mgmt/databases", RESTfulMethod.GET);

        Map<String, Database> databaseMap = new HashMap<String, Database>();

        Databases databases = new Gson().fromJson(response, Databases.class);
        for (Databasis databasis : databases.getDatabases()) {
            databaseMap.put(databasis.getDatabase().getName(), databasis.getDatabase());
        }
        return databaseMap;
    }


    public static Deployment getVoltDBDeployment(String siteName, String dbName) throws ActionException {
        Map<String, Deployment> deployments = null;

        //check config manager
        if (GRMConfigManager.getSiteName().equals(siteName)) {
            deployments = GRMConfigManager.getLocalDeployments();
        } else if (Utils.getCorrespondSiteName(GRMConfigManager.getSiteName()).equals(siteName)) {
            deployments = GRMConfigManager.getRemoteDeployments();
        } else {
            throw new ActionException("The configured siteName[" + siteName + "] is not correct");
        }

        // if cannot get deployment from global configure manager, get the deployment again.
        if (deployments == null) {
            deployments = getVoltDBDeployments(siteName);
        }

        if (logger.isTraceEnabled()) {
            logger.trace("[getVoltDBDeployment] Got deployments = {} from siteName={} dbName={}", new Object[]{deployments, siteName, dbName});
        }

        // get the exact db instance deployment
        if (!deployments.containsKey(dbName)) {
            throw new ActionException("The configured dbInstance[" + dbName + "] is not deployed in site[" + siteName + "]");
        }
        return deployments.get(dbName);
    }

    public static Map<String, Deployment> getVoltDBDeployments(String siteName) throws ActionException {
        String response = RESTfulUtils.invokeVEMRESTAPI(siteName, "/mgmt/deployments", RESTfulMethod.GET);

        if (logger.isTraceEnabled()) {
            logger.trace("[getVoltDBDeployments] response={}, request siteName= {}", response, siteName);
        }
        Map<String, Deployment> deploymentMap = new HashMap<String, Deployment>();

        Deployments deployments = new Gson().fromJson(response, Deployments.class);
        for (Deployment_ deployment_ : deployments.getDeployments()) {
            deploymentMap.put(deployment_.getDeployment().getName(), deployment_.getDeployment());
        }
        return deploymentMap;
    }


    public  static Map<String,String> getVemInfo() throws ActionException{
        Map<String, String> config = GRMConfigManager.getConfig();
        Map<String, String> vemInfo = new HashMap<String, String>();
        String result = null;
        String content = "{  \"subSystem\": \"\","
                + "    \"connector\": \"VoltDB\"},head:{\"_sman_user\":" +
                config.get(Constants.GET_SESSIONSERVER_CONFIG_USER)
                + ",\"_sman_password\":" + config.get(Constants.GET_SESSIONSERVER_CONFIG_PASSWORD)
                + "}";
        try {
            result = getRESTfulInvokeResultByUrl(config.get(Constants.GET_SESSIONSERVER_CONFIG_URL1), content);
        } catch (RESTfulException ex) {
            if (ex.getStatusCode() >= 400) {
                if (null != config.get(Constants.GET_SESSIONSERVER_CONFIG_URL2) && !"".equals(config
                        .get(Constants.GET_SESSIONSERVER_CONFIG_URL2))) {
                    result = getRESTfulInvokeResultByUrl(config.get(Constants.GET_SESSIONSERVER_CONFIG_URL2), content);
                }
            }
        }

        try {
            JSONObject object = new JSONObject(result);
            String resultCode = object.getString("resultCode");
            if ("2001".equals(resultCode)) {
                Object o = object.get("apResult");
                JSONArray list = (JSONArray) ((JSONObject) o).get("record");
                if (list.length() > 0) {
                    JSONObject record = (JSONObject) list.get(0);
                    String connectorInfo = record.getString("connectorInfo");
                    //  "connectorInfo" : "vemHost=16.173.233.197,vemPort=9000,user=spr_db_user,password=spr_db_pass,clientPort=11212",
                    if (!connectorInfo.isEmpty()) {
                        String[] connectors = connectorInfo.split(",");
                        for (String connector : connectors) {
                            String[] s = connector.split("=");
                            if (s.length == 2) {
                                vemInfo.put(s[0], s[1]);
                            }
                        }
                    }
                }
            }
        } catch (JSONException e) {
            logger.error(e.toString());
            throw new ActionException(e.getMessage(), e);
        }
        logger.info(vemInfo.toString());
        return vemInfo;
    }

    public static String getRESTfulInvokeResultByUrl(String url, String content) throws ActionException {
        if (logger.isDebugEnabled()) {
            logger.debug("The url  is:{}", url);
        }
        String host;
        int port = 9800;
        String uri;
        Matcher m = URL_PATTERN.matcher(url);
        if (m.find()) {
            host = m.group(1);
            if (m.groupCount() == 3) {
                port = Integer.parseInt(m.group(2));
                uri = m.group(3);
            } else {
                uri = m.group(2);
            }
        } else {
            throw new ActionException("Illegal URL parameter");
        }
        return RESTfulUtils.getRESTfulInvokeResult(host, port, uri, RESTfulMethod.POST
                .toString(), content);
    }
}
